﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace BadassPhotoAlbum
{
    /***********************************************************************
     * Created by Mike Flood, Adam Reaves and Joseph Alfred
     * This class is used to handle majority of the interaction between the 
     * user interface and the database.
     * *********************************************************************/
    public class Interface
    {
        List<Album> albumList;
        List<Photo> uniquePhotosList;
        Database database;
        int backgroundWorkerSemaphore; //count
        private int MAX_NUM_THREADS = 10;


        //attributes of unique photos
        Album uniquePhotosAlbum;
        private ImageList thumbnailsList = null; //moved here from main form for performance
        private const int THUMBNAILSIZE = 60; // the max size, width and height of thumbnails
        private Mutex thumbNailGenMux;
        private int currentlySelectedAlbum = -2; //current album id selected for state machine

        /**********************************************************************************
         * This is the default constructor that creates the interface class. A new database is
         * created and the list of albums and photos are loaded into seperate list.
         * *******************************************************************************/
        public Interface()
        {
            database = new Database();
            uniquePhotosList = database.getUniquePhotoList();
            albumList = database.GetAllAlbums();
            //once all of the albums and photos are loaded albums are created by loading the infromation
            //from the database. Each album is created by grabing the list of photos from the DB and each
            //album is created one by one.
            foreach (Album albumCreate in albumList)
            {
                
                int albumTempId = albumCreate.getAlbumID();
                List<Photo> tempPhotoList = database.GetAllPhotosFromAlbum(albumTempId);
                foreach (Photo tP in tempPhotoList)
                {
                    albumCreate.addPhoto(tP);//add the photo to the album
                }
            }

            uniquePhotosAlbum = new Album("All Photos", "All Photos", -1);

            thumbnailsList = new ImageList();
            thumbnailsList.ImageSize = new Size(THUMBNAILSIZE, THUMBNAILSIZE); // set the size of the images
            thumbnailsList.ColorDepth = ColorDepth.Depth16Bit;


            backgroundWorkerSemaphore = 0;
            thumbNailGenMux = new Mutex();
            //GenerateThumbnailsHandler = new DoWorkEventHandler(addPhotoThread_DoWork);
            //GenerateThumbnailsComplete = new RunWorkerCompletedEventHandler(addPhotoThread_RunWorkerCompleted);

            foreach(Photo addP in uniquePhotosList)
                uniquePhotosAlbum.addPhoto(addP);

            Thread bWorker = new Thread(InitGenerateThumnails);
            bWorker.IsBackground = true;
           // bWorker.Priority = ThreadPriority.Highest;

            //bWorker.DoWork += new DoWorkEventHandler(InitGenerateThumnails);
            //bWorker.RunWorkerAsync();
            bWorker.Start();
        }
        /// <summary>
        /// This is a background thread that generates all the thumbnails in the unique alubum lis
        /// Written: Adam Reaves
        /// </summary>
        void InitGenerateThumnails()
        {
            //Thread.CurrentThread.Priority = ThreadPriority.Highest;
            //ProgressBar addPhotoProgBar = new ProgressBar();
            //addPhotoProgBar.Visible = true;
            //addPhotoProgBar.Location = new System.Drawing.Point(50, 50);
            //addPhotoProgBar.Name = "progress bar";
            //addPhotoProgBar.Width = 200;
            //addPhotoProgBar.Height = 30;

            //if (uniquePhotosList.Count() > 0)
            //{
            //    addPhotoProgBar.Minimum = 0;
             //   addPhotoProgBar.Maximum = uniquePhotosList.Count() + 1;
             //   addPhotoProgBar.Value = 1;
             //   addPhotoProgBar.Step = 1;
            //}
            //addPhotoProgBar.BringToFront();
            //prepare for thumbnails (Added Adam Reaves)
            foreach (Photo addP in uniquePhotosList)
            {

                //if (backgroundWorkerSemaphore > MAX_NUM_THREADS) //only two threads should be open at a time
                    //
                    //Thread.Sleep(100);
                while (backgroundWorkerSemaphore > MAX_NUM_THREADS)
                    Thread.Sleep(1);
                //BackgroundWorker addThumbnail = new BackgroundWorker();
                //addThumbnail.DoWork += GenerateThumbnailsHandler;
               // addThumbnail.RunWorkerCompleted += GenerateThumbnailsComplete;
                //addThumbnail.RunWorkerAsync(addP);
                Thread addThumbnail = new Thread(addPhotoThread_DoWork);
                addThumbnail.Priority = ThreadPriority.Lowest;
                addThumbnail.Start(addP);
                //backgroundWorkerSemaphore--;

                
                //thumbnailsList.Images.Add(addP.GetPhotoID().ToString(), Image.FromStream(addP.GetFileDescriptor()));
             
                //addPhotoProgBar.Value = addPhotoProgBar.Value + 1;
            }
            return;
            //addPhotoProgBar.Dispose();
            //addPhotoProgBar.Value = 0;
            //addPhotoProgBar.Visible = false;
            
            
        }
        /*
        void InitGenerateThumnails(object sender, DoWorkEventArgs e)
        {
            //ProgressBar addPhotoProg = new ProgressBar(); 
            //addPhotoProg.Visible = true; 
            //addPhotoProg.Location = new System.Drawing.Point(50, 50); 
            //addPhotoProg.Name = "progress bar"; 
            //addPhotoProg.Width = 200; 
            //addPhotoProg.Height = 30; 

            //if (uniquePhotosList.Count() > 0) 
            //{ 
            //    addPhotoProg.Minimum = 1; 
            //    addPhotoProg.Maximum = uniquePhotosList.Count(); 
            //    addPhotoProg.Value = 1; 
            //    addPhotoProg.Step = 1; 
            //} 
            //Controls.Add(addPhotoProg); 
            //addPhotoProg.BringToFront(); 
            //prepare for thumbnails (Added Adam Reaves) 
            foreach (Photo addP in uniquePhotosList)
            {
                BackgroundWorker addPhotoThread = new BackgroundWorker();
                addPhotoThread.DoWork += GenerateThumbnailsHandler;
                addPhotoThread.RunWorkerCompleted += GenerateThumbnailsComplete;
                addPhotoThread.RunWorkerAsync(addP);
                //thumbnailsList.Images.Add(addP.GetPhotoID().ToString(), Image.FromStream(addP.GetFileDescriptor())); 
                uniquePhotosAlbum.addPhoto(addP);
                //addPhotoProg.PerformStep(); 
            }
            //addPhotoProg.Value = 1; 
            //addPhotoProg.Visible = false; 

        }
         * */
        /// <summary>
        /// Function that was supposed to be called when a background worker completed but was
        /// no longer called to use threads instead
        /// Better priority control
        /// Written by: Adam Reaves
        /// </summary>
/*
        private void addPhotoThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
           
            thumbnailsList.Images.Add(image.key, image.image);

            thumbNailGenMux.WaitOne();
            backgroundWorkerSemaphore--;
            thumbNailGenMux.ReleaseMutex();
            
            return;
        }
 * */
        /// <summary>
        /// Thread call to generate thumbnails
        /// Uses an int as a "semaphore" to find out how many photos are being generated
        /// at this moment in time
        /// Written by: Adam Reaves
        /// </summary>
        private void addPhotoThread_DoWork(object e) //object sender, DoWorkEventArgs e
        {
      
            Bitmap reducedBitmap;
            Image addImage;
            //Photo photo = (Photo)e.Argument;
            Photo photo = (Photo)e;
            thumbNailGenMux.WaitOne();
            backgroundWorkerSemaphore++;
            thumbNailGenMux.ReleaseMutex();

            try
            {
                reducedBitmap = new Bitmap(Image.FromStream(photo.GetFileDescriptor()));
                addImage = reducedBitmap.GetThumbnailImage(THUMBNAILSIZE, THUMBNAILSIZE, null, IntPtr.Zero);

            }
            catch
            {
                Thread.CurrentThread.Abort(); //some xp issue
                
                return;
            }

            thumbnailsList.Images.Add(photo.GetPhotoID().ToString(), addImage);
            backgroundWorkerSemaphore--;

            Thread.CurrentThread.Abort(); //make sure it closes XP issue
            return;
        }

        /*********************************************************************************
         * This function takes in the name of the album and the description of the album.
         * These attributes are used to create the album. The album is first added to the
         * DB then is added to the current list of albums.
         * *******************************************************************************/
        public void AddAlbum(string name, string desc)
        {
            name.Trim(); //remove leading and following white spaces (added AMR)
            Album newalbum = new Album(name,desc, database.RequestNextAlbumID());
            database.AddAlbum(newalbum);
            albumList.Add(newalbum);
        }

        /**********************************************************************************
         * This function removes the album from the databas and the current list of photos
         * based on the selected album id.
         * *******************************************************************************/
        public void DeleteAlbum(int albumid)
        {
            
            for (int i = 0; i < albumList.Count; i++)
            {
                if (albumList[i].getAlbumID() == albumid)
                {
                    //remove all the pictures from the album first
                    //generate a list of photo IDs to delete
                    List<int> photoIdlist = new List<int>();

                    foreach (Photo ph in albumList[i].getPhotoList())
                        photoIdlist.Add(ph.GetPhotoID());
                    for (int j = 0; j < photoIdlist.Count; j++)
                        DeletePhoto(albumid, photoIdlist[j]);

                    albumList.RemoveAt(i);
                    break;
                }
            }
            database.DeleteAlbum(albumid);
        }
        /*********************************************************************************
        * This function takes in the name of the album
        * And checks the DB to see if it exists already
        * *******************************************************************************/
        public bool IsPresentAlbumByName(string name)
        {
            //scrub all the spaces out and make it lower case
            string sName = name.Trim().ToLower();
            foreach (Album a in albumList)
            {
                if ((a.AlbumName.ToLower()) == sName)
                    return true;
            }
            return false;
        }
        /*********************************************************************************
        * This funciton takes an albumid and sorts it depending on which direciton is passed to 
        * it
        * Written by: Adam Reaves
        * *******************************************************************************/
        public void sortAlbumPhotosByName(int albumID, PhotoSortOrder order)
        {
            if (albumID == -1) //we don't modify the all photos album
                return;

            Album album = this.GetAlbumById(albumID);

            album.SortAlbumbyName(order);
            database.ClearAndReWriteAlbum(album);
        }
        /*********************************************************************************
        * Takes an album ID and a photoID in that album and moves it to the index index
        * in the album
        * Written by: Adam Reaves
        * *******************************************************************************/
        public void MovePhotoInAlbumtoIndex(int albumid, int photoid, int index)
        {
            if (CurrentlySelectedAlbum == -1) //all albums and ignore
                return;

            Album modAlbum = GetAlbumById(albumid); //capture the album we need 

            modAlbum.movePhototoIndex(photoid, index);
            database.MovePhotoInAlbumToIndex(albumid, photoid, index-1); //database is 0 based
        }
        /*********************************************************************************
        * Does the same thing as addphototoalbum normal but moves it to index index once it's done
        * Written by: Adam REaves
        * *******************************************************************************/
        public bool AddPhotoToAlbum(int albumid, string name, string description, string path, int index)
        {
            //lets add it to the album with an already written function
            if (!AddPhotoToAlbum(albumid, name, description, path)) //if it gets added to the album
                return false;
            //due to the fact it passed the above error check, all arguments are valid and don't need to be checked

            if (index == -1)
                return true; //there's no arranging to do
            
            //get the album that just got added to
            Album modAlbum = GetAlbumById(albumid);

            //we know that there is a photo in the list and know it's the last based on the first call
            int photoid = modAlbum.getPhotoList().Last().GetPhotoID();

            //move it!
            this.MovePhotoInAlbumtoIndex(albumid, photoid, index);

            return true;
        }
        /************************************************************************************
        * This function is used to add a photo to a selected album. It takes the name of 
        * the photo, description, path of the photo and albumID that it belongs to.
        * **********************************************************************************/
        public bool AddPhotoToAlbum(int albumid, string name, string description, string path)
        {
            Stream tempFile;
            try //Adam Reaves - Fixed Fletcher' odd add photo bug
            {
                tempFile = File.Open(path, FileMode.Open);//store the current path of the photo
            }
            catch
            {
                return false; //it it won't open, we can't add it!
            }
            int hashVal = Database.GenerateHash(tempFile);//generate a hash alue for the pic 
            
            Photo uniquep = null;//the photo is NOT unique at this point
            
            
            if (database.CheckPhotoExistance(hashVal))
            {
                //close the file still (A little bit of garbage collection)
                tempFile.Close(); //we don't need this anymore

                //find the photo in the unique list
                foreach (Photo hashP in uniquePhotosList)
                {
                    if (hashP.GetHashValue() == hashVal)
                    {
                        uniquep = hashP;
                        break;
                    }
                }
            }
            else
            {
                int nextPhotoID = database.RequestNextPhotoID();
                //generates a new path for the photo to be stores in a 'secret location'.
                string newPath = database.GetSecretLocal() + nextPhotoID.ToString();
                string[] extensionCheck = path.Split('.');

                if(extensionCheck[extensionCheck.Count() -1] == "gif")
                    return false; //we don't want GIFS

                tempFile.Close(); //we don't need this anymore

                try //Adam Reaves - Fixed issue on adding a "correct" file than cannot be made into an image
                {
                    Image.FromFile(path);
                    File.Copy(path, newPath);//copy the path and store it as a variable
                }
                catch
                {
                    return false;
                }
                


                //create a unique photo in the DB with the new path, description and name
                uniquep = new Photo(name, nextPhotoID, description, newPath);

                //add to the unique photo list and uniquePhotosAlbum
                uniquePhotosList.Add(uniquep);
                uniquePhotosAlbum.addPhoto(uniquep);

                //Bitmap reducedBitmap = new Bitmap(Image.FromStream(uniquep.GetFileDescriptor()));
                //thumbnailsList.Images.Add(reducedBitmap.GetThumbnailImage(THUMBNAILSIZE, THUMBNAILSIZE, null, IntPtr.Zero));

                Thread addImageThread = new Thread(addPhotoThread_DoWork);
                addImageThread.IsBackground = true;
                //addImageThread.DoWork += GenerateThumbnailsHandler;
                //addImageThread.RunWorkerCompleted += GenerateThumbnailsComplete;
                while (backgroundWorkerSemaphore > MAX_NUM_THREADS) //limit the # of simul threads running
                    Thread.Sleep(1);
                addImageThread.Start(uniquep);
                //addImageThread.RunWorkerAsync(uniquep);
            }

           
            //getAlbumById(albumid).getPhotoList().Add(uniquep);
            Album modAlbum = GetAlbumById(albumid);
            if(modAlbum.addPhoto(uniquep))
                database.AddPhotoToAlbum(albumid, uniquep);//add the photo to the database

            return true;

        }
        /*********************************************************************************
        * Adds a photo class to the album albumID
        * Written by: Adam Reaves
        * *******************************************************************************/
        public void AddPhotoToAlbum(int albumID, Photo addPhoto)
        {
            if (currentlySelectedAlbum == -1)
                return; //don't modify the all photos album

            Album addAlbum = this.GetAlbumById(albumID);

            if(addAlbum.addPhoto(addPhoto))
                database.AddPhotoToAlbum(albumID, addPhoto);//add the photo to the database
        }
        /*********************************************************************************
        * Does the same as AddPHotoToAlbum but moves it to index index when done
        * Written by: Adam Reaves
        * *******************************************************************************/
        public void AddPhotoToAlbum(int albumID, Photo addPhoto, int index)
        {
            if(currentlySelectedAlbum == -1) //all albums
                return; //don't modify it

            AddPhotoToAlbum(albumID, addPhoto);

            Album modAlbum = GetAlbumById(albumID);

            if (modAlbum == null)
                return; //album doesn' exist

            this.MovePhotoInAlbumtoIndex(albumID, addPhoto.GetPhotoID(), index);
        }

        /*************************************************************************************
        * This function is used to copy selected photos from one album to another.It takes the 
        * albumId and the photoId.
        * **********************************************************************************/
        public void DragPhotosToAlbum(int albumId, int pID)
        {
            
            Album modAlbum = GetAlbumById(albumId);
            Photo dragPhoto = GetPhotoByID(pID);
            if(modAlbum.addPhoto(dragPhoto))
                database.AddPhotoToAlbum(albumId, dragPhoto);
        }
        /*********************************************************************************
        * Adds a straight filename to album and verifies it's valid
        * Written by Adam Reaves
        * *******************************************************************************/
        public void DragFileToAlbum(string filename, int albumID)
        {
            if (albumID == -1) //all albums
                return; //cant drag to all albums

            string[] splitResult = filename.Split('.');
            if (splitResult.Count() == 1) //no file extension
                return;
            else
                splitResult = splitResult[splitResult.Count() - 2].Split('\\');
            

            AddPhotoToAlbum(albumID, splitResult[splitResult.Count() - 1], "", filename);
        }
        /*********************************************************************************
        * Does the same thing as DragFileToAlbum with less parameters
        * But moves it to index index after it is done
        * Written by: Adam Reaves
        * *******************************************************************************/
        public void DragFileToAlbum(string filename, int albumID, int index)
        {
            if (albumID == -1) //all albums
                return; //cant drag to all albums

            if (index == -1)
            {
                DragFileToAlbum(filename, albumID);
                return;
            }

            string[] splitResult = filename.Split('.');
            if (splitResult.Count() == 1) //no file extension
                return;
            else
                splitResult = splitResult[splitResult.Count() - 2].Split('\\');

            if (AddPhotoToAlbum(albumID, splitResult[splitResult.Count() - 1], "", filename)) //if it was added
                MovePhotoInAlbumtoIndex(albumID, GetAlbumById(albumID).getPhotoList().Last().GetPhotoID(), index);
        }

        /*************************************************************************************
         * This function is used to delete a photo from the DB and album. It takes the albumId
         * and the photoId.
         * **********************************************************************************/
        public void DeletePhoto(int albumid, int photoid)
        {
            Album remAlbum = this.GetAlbumById(albumid);
            Photo remPhoto = remAlbum.getPhotobyID(photoid);
            remAlbum.deletePhoto(photoid);
            database.DeletePhotoFromAlbum(albumid, photoid);

            //remove from uniques if it no longer exists in database
            if(!database.CheckPhotoExistance(remPhoto.GetHashValue()))
            {
                for (int i = 0; i < GetUniquePhotoListCount(); i++)
                {
                    if (remPhoto.GetPhotoID() == uniquePhotosAlbum.getPhotoList()[i].GetPhotoID())
                    {
                        //remove it from the secret location too
                        remPhoto.GetFileDescriptor().Close();
                        //remove it from the unique photo list
                        uniquePhotosAlbum.deletePhoto(remPhoto.GetPhotoID());
                        File.Delete(database.GetSecretLocal() + remPhoto.GetPhotoID());
                        File.Delete(database.GetSecretLocal() + remPhoto.GetPhotoID() + ".thumb");
                        break;
                    }
                }
            }
        }
       
        /**************************************************************************************
         * This function is used to modify the name and description of the selected photo. It takes
         * the photoId, name and description. It modifys the photo in the DB.
         * ***********************************************************************************/
        public void ModifyPhoto(int photoid, string name, string desc)
        {
            Photo tP = GetPhotoByID(photoid);
            tP.PhotoName = name;
            tP.PhotoDescription = desc;
            database.modifyPhoto(photoid, tP);
        }

        /**************************************************************************************
         * This function is used to modify the name and description of the selected album. It takes
         * the album, name and description. It modifys the album in the DB.
         * ***********************************************************************************/
        public void ModifyAlbum(int albumid, string name, string desc)
        {
            Album tA = GetAlbumById(albumid);
            tA.AlbumName = name;
            tA.AlbumDescription = desc;
            database.ModifyAlbum(albumid, tA);
        }

        //Returns the list of albums
        public List<Album> GetAlbumList()
        {
            return albumList;
        }

        //Returns the list of uniquePhotos
        public Album GetUniquePhotoAlbum()
        {
            return uniquePhotosAlbum;
        }

        /**********************************************************************
         * This function gets (and returns) the photo based on the selected id.
         * ********************************************************************/
        public Photo GetPhotoByID(int id)
        {
            //searches through the unique photo list and returns the photo with the matching photoId
            for (int i = 0; i < uniquePhotosList.Count; i++)
            {
                if (uniquePhotosList[i].GetPhotoID() == id)
                {
                    return uniquePhotosList[i];
                }
            }
            return null; // should never happen if used correctly (means the photo is NOT in the list)
        }
        
        /*********************************************************************************
         * This function takes the albumId and is compared to the other albums in the albumList
         * and the selected index is then returned.
         * *****************************************************************************/
        public Album GetAlbumById(int albumid)
        {
            for (int i = 0; i < albumList.Count; i++)
            {
                if (albumList[i].getAlbumID() == albumid)
                {
                    return albumList[i];
                }
            }
            return null; // should never happen (but that's never going to happen)
        }
        public Album GetAlbumByName(string sName)
        {
            for (int i = 0; i < albumList.Count; i++)
            {
                if (albumList[i].AlbumName == sName)
                {
                    return albumList[i];
                }
            }
            return null; // indicates that the album doesn't exist
        }

        /*****************************************************************************
         * This function counts the number of unique photos and returns the count
         * **************************************************************************/
        public int GetUniquePhotoListCount()
        {
            return uniquePhotosList.Count;
        }
        /*********************************************************************************
         * If done generating returns the big image lsit
         * if it's not, it returns null indicating that it's not done generating
         * Written by Adam Reaves
         * *******************************************************************************/
        public ImageList GetThumbnailImageList()
        {
            if (!thumbNailGenMux.WaitOne(1))
                return null;

            if (backgroundWorkerSemaphore > 0)
            {
                thumbNailGenMux.ReleaseMutex();
                return null;
            }
            if (thumbnailsList.Images.Count < uniquePhotosAlbum.getPhotoList().Count)
            {
                thumbNailGenMux.ReleaseMutex();
                return null;
            }

            thumbNailGenMux.ReleaseMutex();
            return thumbnailsList;
        }
        /*********************************************************************************
        * Gets a photoID based on its' name Alone
        * Written by: Steven Gonzalez
        * *******************************************************************************/
        public int GetPhotoIDByName(string name)
        {
            foreach (Album a in albumList)
            {
                if (a.AlbumName == name)
                    return a.getAlbumID();
            }
            return -1;
        }
        /*********************************************************************************
        * Last minute property to denote currently selected albun
         * Written by: Adam Reaves
         * *******************************************************************************/
        public int CurrentlySelectedAlbum
        {
            get { return currentlySelectedAlbum; }
            set { currentlySelectedAlbum = value; }
        }
        /*
        private Image addPhototoImageList(object p)
        {
            Photo photo = (Photo)p;
            return Image.FromStream(photo.GetFileDescriptor());
        }
         * */
    }
}

